{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introduction to Quantitative Finance\n",
    "\n",
    "Copyright (c) 2019 Python Charmers Pty Ltd, Australia, <https://pythoncharmers.com>. All rights reserved.\n",
    "\n",
    "<img src=\"img/python_charmers_logo.png\" width=\"300\" alt=\"Python Charmers Logo\">\n",
    "\n",
    "Published under the Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) license. See `LICENSE.md` for details.\n",
    "\n",
    "Sponsored by Tibra Global Services, <https://tibra.com>\n",
    "\n",
    "<img src=\"img/tibra_logo.png\" width=\"300\" alt=\"Tibra Logo\">\n",
    "\n",
    "\n",
    "## Module 1.1: Distributions and Random Processes\n",
    "\n",
    "### 1.1.3: Moments\n",
    "\n",
    "Moments describe distributions. We'll focus on the normal (and normal-ish) distributions for now, but will look at other distributions later.\n",
    "\n",
    "A normal distribution is fully described by the first two moments, which are the mean and the variance. Reviewing the help for the `stats.norm` function, we can see these are the only two parameters we can input (see the docstring of the function)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run setup.ipy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "    Note: it's worth opening up setup.ipy and seeing what's in there. This file will be run at the start of most of our notebooks.\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1;31mSignature:\u001b[0m       \u001b[0mstats\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mnorm\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mType:\u001b[0m            norm_gen\n",
      "\u001b[1;31mString form:\u001b[0m     <scipy.stats._continuous_distns.norm_gen object at 0x0000027E71740650>\n",
      "\u001b[1;31mFile:\u001b[0m            c:\\python311\\lib\\site-packages\\scipy\\stats\\_continuous_distns.py\n",
      "\u001b[1;31mDocstring:\u001b[0m      \n",
      "A normal continuous random variable.\n",
      "\n",
      "The location (``loc``) keyword specifies the mean.\n",
      "The scale (``scale``) keyword specifies the standard deviation.\n",
      "\n",
      "As an instance of the `rv_continuous` class, `norm` object inherits from it\n",
      "a collection of generic methods (see below for the full list),\n",
      "and completes them with details specific for this particular distribution.\n",
      "\n",
      "Methods\n",
      "-------\n",
      "rvs(loc=0, scale=1, size=1, random_state=None)\n",
      "    Random variates.\n",
      "pdf(x, loc=0, scale=1)\n",
      "    Probability density function.\n",
      "logpdf(x, loc=0, scale=1)\n",
      "    Log of the probability density function.\n",
      "cdf(x, loc=0, scale=1)\n",
      "    Cumulative distribution function.\n",
      "logcdf(x, loc=0, scale=1)\n",
      "    Log of the cumulative distribution function.\n",
      "sf(x, loc=0, scale=1)\n",
      "    Survival function  (also defined as ``1 - cdf``, but `sf` is sometimes more accurate).\n",
      "logsf(x, loc=0, scale=1)\n",
      "    Log of the survival function.\n",
      "ppf(q, loc=0, scale=1)\n",
      "    Percent point function (inverse of ``cdf`` --- percentiles).\n",
      "isf(q, loc=0, scale=1)\n",
      "    Inverse survival function (inverse of ``sf``).\n",
      "moment(order, loc=0, scale=1)\n",
      "    Non-central moment of the specified order.\n",
      "stats(loc=0, scale=1, moments='mv')\n",
      "    Mean('m'), variance('v'), skew('s'), and/or kurtosis('k').\n",
      "entropy(loc=0, scale=1)\n",
      "    (Differential) entropy of the RV.\n",
      "fit(data)\n",
      "    Parameter estimates for generic data.\n",
      "    See `scipy.stats.rv_continuous.fit <https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.rv_continuous.fit.html#scipy.stats.rv_continuous.fit>`__ for detailed documentation of the\n",
      "    keyword arguments.\n",
      "expect(func, args=(), loc=0, scale=1, lb=None, ub=None, conditional=False, **kwds)\n",
      "    Expected value of a function (of one argument) with respect to the distribution.\n",
      "median(loc=0, scale=1)\n",
      "    Median of the distribution.\n",
      "mean(loc=0, scale=1)\n",
      "    Mean of the distribution.\n",
      "var(loc=0, scale=1)\n",
      "    Variance of the distribution.\n",
      "std(loc=0, scale=1)\n",
      "    Standard deviation of the distribution.\n",
      "interval(confidence, loc=0, scale=1)\n",
      "    Confidence interval with equal areas around the median.\n",
      "\n",
      "Notes\n",
      "-----\n",
      "The probability density function for `norm` is:\n",
      "\n",
      ".. math::\n",
      "\n",
      "    f(x) = \\frac{\\exp(-x^2/2)}{\\sqrt{2\\pi}}\n",
      "\n",
      "for a real number :math:`x`.\n",
      "\n",
      "The probability density above is defined in the \"standardized\" form. To shift\n",
      "and/or scale the distribution use the ``loc`` and ``scale`` parameters.\n",
      "Specifically, ``norm.pdf(x, loc, scale)`` is identically\n",
      "equivalent to ``norm.pdf(y) / scale`` with\n",
      "``y = (x - loc) / scale``. Note that shifting the location of a distribution\n",
      "does not make it a \"noncentral\" distribution; noncentral generalizations of\n",
      "some distributions are available in separate classes.\n",
      "\n",
      "Examples\n",
      "--------\n",
      ">>> import numpy as np\n",
      ">>> from scipy.stats import norm\n",
      ">>> import matplotlib.pyplot as plt\n",
      ">>> fig, ax = plt.subplots(1, 1)\n",
      "\n",
      "Calculate the first four moments:\n",
      "\n",
      "\n",
      ">>> mean, var, skew, kurt = norm.stats(moments='mvsk')\n",
      "\n",
      "Display the probability density function (``pdf``):\n",
      "\n",
      ">>> x = np.linspace(norm.ppf(0.01),\n",
      "...                 norm.ppf(0.99), 100)\n",
      ">>> ax.plot(x, norm.pdf(x),\n",
      "...        'r-', lw=5, alpha=0.6, label='norm pdf')\n",
      "\n",
      "Alternatively, the distribution object can be called (as a function)\n",
      "to fix the shape, location and scale parameters. This returns a \"frozen\"\n",
      "RV object holding the given parameters fixed.\n",
      "\n",
      "Freeze the distribution and display the frozen ``pdf``:\n",
      "\n",
      ">>> rv = norm()\n",
      ">>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf')\n",
      "\n",
      "Check accuracy of ``cdf`` and ``ppf``:\n",
      "\n",
      ">>> vals = norm.ppf([0.001, 0.5, 0.999])\n",
      ">>> np.allclose([0.001, 0.5, 0.999], norm.cdf(vals))\n",
      "True\n",
      "\n",
      "Generate random numbers:\n",
      "\n",
      ">>> r = norm.rvs(size=1000)\n",
      "\n",
      "And compare the histogram:\n",
      "\n",
      ">>> ax.hist(r, density=True, bins='auto', histtype='stepfilled', alpha=0.2)\n",
      ">>> ax.set_xlim([x[0], x[-1]])\n",
      ">>> ax.legend(loc='best', frameon=False)\n",
      ">>> plt.show()\n",
      "\u001b[1;31mClass docstring:\u001b[0m\n",
      "A normal continuous random variable.\n",
      "\n",
      "The location (``loc``) keyword specifies the mean.\n",
      "The scale (``scale``) keyword specifies the standard deviation.\n",
      "\n",
      "%(before_notes)s\n",
      "\n",
      "Notes\n",
      "-----\n",
      "The probability density function for `norm` is:\n",
      "\n",
      ".. math::\n",
      "\n",
      "    f(x) = \\frac{\\exp(-x^2/2)}{\\sqrt{2\\pi}}\n",
      "\n",
      "for a real number :math:`x`.\n",
      "\n",
      "%(after_notes)s\n",
      "\n",
      "%(example)s\n",
      "\u001b[1;31mCall docstring:\u001b[0m \n",
      "Freeze the distribution for the given arguments.\n",
      "\n",
      "Parameters\n",
      "----------\n",
      "arg1, arg2, arg3,... : array_like\n",
      "    The shape parameter(s) for the distribution.  Should include all\n",
      "    the non-optional arguments, may include ``loc`` and ``scale``.\n",
      "\n",
      "Returns\n",
      "-------\n",
      "rv_frozen : rv_frozen instance\n",
      "    The frozen distribution."
     ]
    }
   ],
   "source": [
    "stats.norm?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As noted in that description, the first moment, the mean, is referred to as the location. It specifies where the normal distribution is centred."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "  #altair-viz-2d7d470f7ad041528292e3c01e79eaf1.vega-embed {\n",
       "    width: 100%;\n",
       "    display: flex;\n",
       "  }\n",
       "\n",
       "  #altair-viz-2d7d470f7ad041528292e3c01e79eaf1.vega-embed details,\n",
       "  #altair-viz-2d7d470f7ad041528292e3c01e79eaf1.vega-embed details summary {\n",
       "    position: relative;\n",
       "  }\n",
       "</style>\n",
       "<div id=\"altair-viz-2d7d470f7ad041528292e3c01e79eaf1\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-2d7d470f7ad041528292e3c01e79eaf1\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-2d7d470f7ad041528292e3c01e79eaf1\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm/vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm/vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm/vega-lite@5.16.3?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm/vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function maybeLoadScript(lib, version) {\n",
       "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
       "      return (VEGA_DEBUG[key] == version) ?\n",
       "        Promise.resolve(paths[lib]) :\n",
       "        new Promise(function(resolve, reject) {\n",
       "          var s = document.createElement('script');\n",
       "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "          s.async = true;\n",
       "          s.onload = () => {\n",
       "            VEGA_DEBUG[key] = version;\n",
       "            return resolve(paths[lib]);\n",
       "          };\n",
       "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "          s.src = paths[lib];\n",
       "        });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else {\n",
       "      maybeLoadScript(\"vega\", \"5\")\n",
       "        .then(() => maybeLoadScript(\"vega-lite\", \"5.16.3\"))\n",
       "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 300, \"continuousHeight\": 300}}, \"layer\": [{\"data\": {\"url\": \"altair-data-7d317c59873e1fc579f8c5c283bebcf1.json\", \"format\": {\"type\": \"json\"}}, \"mark\": {\"type\": \"bar\"}, \"encoding\": {\"color\": {\"value\": \"red\"}, \"x\": {\"bin\": {\"maxbins\": 100}, \"field\": \"value\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"type\": \"quantitative\"}}}, {\"data\": {\"url\": \"altair-data-1ca134930e9e9d50088afcd31b7ffdb6.json\", \"format\": {\"type\": \"json\"}}, \"mark\": {\"type\": \"bar\"}, \"encoding\": {\"color\": {\"value\": \"blue\"}, \"x\": {\"bin\": {\"maxbins\": 100}, \"field\": \"value\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"type\": \"quantitative\"}}}], \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.16.3.json\"}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.LayerChart(...)"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def plot_histogram_normal(mean, standard_deviation, color):\n",
    "    distribution = stats.norm(mean, standard_deviation)\n",
    "    normal_values = pd.DataFrame({\"value\": distribution.rvs(10000)})\n",
    "\n",
    "    chart = alt.Chart(normal_values).mark_bar().encode(\n",
    "        alt.X(\"value\", bin=alt.Bin(maxbins=100)),\n",
    "        y='count()',\n",
    "        color=alt.value(color)\n",
    "    )\n",
    "    return chart\n",
    "\n",
    "chart_1 = plot_histogram_normal(0, 1, \"red\")\n",
    "chart_2 = plot_histogram_normal(3, 1, \"blue\")\n",
    "chart_1 + chart_2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The mean is the expected value of the distribution. Given all other things equal, if we chose *n* values randomly from this distribution, the average value (mean) would be equal to the mean of the distribution. This might seem like circular knowledge, but note the values are computed in different ways:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "actual_mean = 57\n",
    "standard_deviation = random.random() * 10\n",
    "N_TRIALS = 100000\n",
    "\n",
    "distribution = stats.norm(actual_mean, standard_deviation)\n",
    "normal_values = distribution.rvs(N_TRIALS)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "56.99843855534003"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.mean(normal_values)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The actual mean was 57, while the computed mean was 56.998\n",
      "This gives an error of -0.002\n"
     ]
    }
   ],
   "source": [
    "error = np.mean(normal_values) - actual_mean\n",
    "print(\"The actual mean was {actual_mean}, while the computed mean was {computed_mean:.3f}\".format(\n",
    "    actual_mean=actual_mean, computed_mean=np.mean(normal_values)))\n",
    "print(\"This gives an error of {error:.3f}\".format(error=error))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the mean is not the median, although in a normal distribution, they are usually about the same (and theoretically they are the same value). The median is not a \"moment\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "57.00268508847411"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.median(normal_values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The second moment of a normal distribution is the variance, also known as the scale factor of the distribution. It is the expected value of the squared difference between a random value and the mean:\n",
    "\n",
    "$V=\\frac{1}{n}\\sum^n_{i=0}(X_i-\\mu)^2$\n",
    "\n",
    "Note that the square in the result makes the unit squared as well. For instance, if our measurements were in metres $m$, the variance would be in metres squared, $m^2$. As a result, it's not directly comparable to the initial value. For instance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "12.562341516923786"
      ]
     },
     "execution_count": 41,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "V = np.var(normal_values)\n",
    "V"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can not directly compare this to our original units, i.e. we can not say the variance is \"about 0.5% of the mean\".\n",
    "Such a statment is meaningless as the units are different. \n",
    "For that reason, we usually use the square root of the variance, known as the standard deviation, which is in the same units as X, and is therefore comparable in such a way:\n",
    "\n",
    "$V=\\sigma^2=\\frac{1}{n}\\sum^n_{i=0}(X_i-\\mu)^2$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is this \"standard deviation\" that is the second input into our `stats.norm` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "  #altair-viz-7e961f3ee2ff4fca875a2f26e3ed64d5.vega-embed {\n",
       "    width: 100%;\n",
       "    display: flex;\n",
       "  }\n",
       "\n",
       "  #altair-viz-7e961f3ee2ff4fca875a2f26e3ed64d5.vega-embed details,\n",
       "  #altair-viz-7e961f3ee2ff4fca875a2f26e3ed64d5.vega-embed details summary {\n",
       "    position: relative;\n",
       "  }\n",
       "</style>\n",
       "<div id=\"altair-viz-7e961f3ee2ff4fca875a2f26e3ed64d5\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-7e961f3ee2ff4fca875a2f26e3ed64d5\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-7e961f3ee2ff4fca875a2f26e3ed64d5\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm/vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm/vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm/vega-lite@5.16.3?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm/vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function maybeLoadScript(lib, version) {\n",
       "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
       "      return (VEGA_DEBUG[key] == version) ?\n",
       "        Promise.resolve(paths[lib]) :\n",
       "        new Promise(function(resolve, reject) {\n",
       "          var s = document.createElement('script');\n",
       "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "          s.async = true;\n",
       "          s.onload = () => {\n",
       "            VEGA_DEBUG[key] = version;\n",
       "            return resolve(paths[lib]);\n",
       "          };\n",
       "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "          s.src = paths[lib];\n",
       "        });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else {\n",
       "      maybeLoadScript(\"vega\", \"5\")\n",
       "        .then(() => maybeLoadScript(\"vega-lite\", \"5.16.3\"))\n",
       "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 300, \"continuousHeight\": 300}}, \"layer\": [{\"data\": {\"url\": \"altair-data-9f47bcda2d50a5800efbcdce7d65e51b.json\", \"format\": {\"type\": \"json\"}}, \"mark\": {\"type\": \"bar\"}, \"encoding\": {\"color\": {\"value\": \"green\"}, \"x\": {\"bin\": {\"maxbins\": 100}, \"field\": \"value\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"type\": \"quantitative\"}}}, {\"data\": {\"url\": \"altair-data-3e327d5f6d70ccfd2dc6e97324b3f5cc.json\", \"format\": {\"type\": \"json\"}}, \"mark\": {\"type\": \"bar\"}, \"encoding\": {\"color\": {\"value\": \"orange\"}, \"x\": {\"bin\": {\"maxbins\": 100}, \"field\": \"value\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"type\": \"quantitative\"}}}], \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.16.3.json\"}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.LayerChart(...)"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chart_3 = plot_histogram_normal(0, 1, \"green\")\n",
    "chart_4 = plot_histogram_normal(6, 2, \"orange\")\n",
    "chart_3 + chart_4"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The larger standard deviation makes the distribution more spread out, but it is the same shape, simply \"scaled\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Further Moments\n",
    "\n",
    "There are two further moments in common use. The third sequentially is called the skew.\n",
    "It can be visualised as \"pulling\" the distribution to the left (negative skew) or right (positive skew).\n",
    "\n",
    "A normal distribution is symmetrical, and has a skew of 0. This is why it does not appear in the equation or function calls to generate the normal distribution.\n",
    "\n",
    "The fourth standardised moment is the kurtosis, more commonly seen in financial data than in many other datasets. A higher value indicates \"fatter tails\" than a standard normal distribution. The kurtosis value of a normal distribution is always 3 - we consider this our baseline when interpreting the kurtosis value of other distributions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<scipy.stats._distn_infrastructure.rv_continuous_frozen at 0x27e749b3c10>"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "stats.skewnorm(4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1;31mSignature:\u001b[0m       \u001b[0mstats\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mskewnorm\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwds\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mType:\u001b[0m            skew_norm_gen\n",
      "\u001b[1;31mString form:\u001b[0m     <scipy.stats._continuous_distns.skew_norm_gen object at 0x0000027E717C8210>\n",
      "\u001b[1;31mFile:\u001b[0m            c:\\python311\\lib\\site-packages\\scipy\\stats\\_continuous_distns.py\n",
      "\u001b[1;31mDocstring:\u001b[0m      \n",
      "A skew-normal random variable.\n",
      "\n",
      "As an instance of the `rv_continuous` class, `skewnorm` object inherits from it\n",
      "a collection of generic methods (see below for the full list),\n",
      "and completes them with details specific for this particular distribution.\n",
      "\n",
      "Methods\n",
      "-------\n",
      "rvs(a, loc=0, scale=1, size=1, random_state=None)\n",
      "    Random variates.\n",
      "pdf(x, a, loc=0, scale=1)\n",
      "    Probability density function.\n",
      "logpdf(x, a, loc=0, scale=1)\n",
      "    Log of the probability density function.\n",
      "cdf(x, a, loc=0, scale=1)\n",
      "    Cumulative distribution function.\n",
      "logcdf(x, a, loc=0, scale=1)\n",
      "    Log of the cumulative distribution function.\n",
      "sf(x, a, loc=0, scale=1)\n",
      "    Survival function  (also defined as ``1 - cdf``, but `sf` is sometimes more accurate).\n",
      "logsf(x, a, loc=0, scale=1)\n",
      "    Log of the survival function.\n",
      "ppf(q, a, loc=0, scale=1)\n",
      "    Percent point function (inverse of ``cdf`` --- percentiles).\n",
      "isf(q, a, loc=0, scale=1)\n",
      "    Inverse survival function (inverse of ``sf``).\n",
      "moment(order, a, loc=0, scale=1)\n",
      "    Non-central moment of the specified order.\n",
      "stats(a, loc=0, scale=1, moments='mv')\n",
      "    Mean('m'), variance('v'), skew('s'), and/or kurtosis('k').\n",
      "entropy(a, loc=0, scale=1)\n",
      "    (Differential) entropy of the RV.\n",
      "fit(data)\n",
      "    Parameter estimates for generic data.\n",
      "    See `scipy.stats.rv_continuous.fit <https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.rv_continuous.fit.html#scipy.stats.rv_continuous.fit>`__ for detailed documentation of the\n",
      "    keyword arguments.\n",
      "expect(func, args=(a,), loc=0, scale=1, lb=None, ub=None, conditional=False, **kwds)\n",
      "    Expected value of a function (of one argument) with respect to the distribution.\n",
      "median(a, loc=0, scale=1)\n",
      "    Median of the distribution.\n",
      "mean(a, loc=0, scale=1)\n",
      "    Mean of the distribution.\n",
      "var(a, loc=0, scale=1)\n",
      "    Variance of the distribution.\n",
      "std(a, loc=0, scale=1)\n",
      "    Standard deviation of the distribution.\n",
      "interval(confidence, a, loc=0, scale=1)\n",
      "    Confidence interval with equal areas around the median.\n",
      "\n",
      "Notes\n",
      "-----\n",
      "The pdf is::\n",
      "\n",
      "    skewnorm.pdf(x, a) = 2 * norm.pdf(x) * norm.cdf(a*x)\n",
      "\n",
      "`skewnorm` takes a real number :math:`a` as a skewness parameter\n",
      "When ``a = 0`` the distribution is identical to a normal distribution\n",
      "(`norm`). `rvs` implements the method of [1]_.\n",
      "\n",
      "The probability density above is defined in the \"standardized\" form. To shift\n",
      "and/or scale the distribution use the ``loc`` and ``scale`` parameters.\n",
      "Specifically, ``skewnorm.pdf(x, a, loc, scale)`` is identically\n",
      "equivalent to ``skewnorm.pdf(y, a) / scale`` with\n",
      "``y = (x - loc) / scale``. Note that shifting the location of a distribution\n",
      "does not make it a \"noncentral\" distribution; noncentral generalizations of\n",
      "some distributions are available in separate classes.\n",
      "\n",
      "Examples\n",
      "--------\n",
      ">>> import numpy as np\n",
      ">>> from scipy.stats import skewnorm\n",
      ">>> import matplotlib.pyplot as plt\n",
      ">>> fig, ax = plt.subplots(1, 1)\n",
      "\n",
      "Calculate the first four moments:\n",
      "\n",
      ">>> a = 4\n",
      ">>> mean, var, skew, kurt = skewnorm.stats(a, moments='mvsk')\n",
      "\n",
      "Display the probability density function (``pdf``):\n",
      "\n",
      ">>> x = np.linspace(skewnorm.ppf(0.01, a),\n",
      "...                 skewnorm.ppf(0.99, a), 100)\n",
      ">>> ax.plot(x, skewnorm.pdf(x, a),\n",
      "...        'r-', lw=5, alpha=0.6, label='skewnorm pdf')\n",
      "\n",
      "Alternatively, the distribution object can be called (as a function)\n",
      "to fix the shape, location and scale parameters. This returns a \"frozen\"\n",
      "RV object holding the given parameters fixed.\n",
      "\n",
      "Freeze the distribution and display the frozen ``pdf``:\n",
      "\n",
      ">>> rv = skewnorm(a)\n",
      ">>> ax.plot(x, rv.pdf(x), 'k-', lw=2, label='frozen pdf')\n",
      "\n",
      "Check accuracy of ``cdf`` and ``ppf``:\n",
      "\n",
      ">>> vals = skewnorm.ppf([0.001, 0.5, 0.999], a)\n",
      ">>> np.allclose([0.001, 0.5, 0.999], skewnorm.cdf(vals, a))\n",
      "True\n",
      "\n",
      "Generate random numbers:\n",
      "\n",
      ">>> r = skewnorm.rvs(a, size=1000)\n",
      "\n",
      "And compare the histogram:\n",
      "\n",
      ">>> ax.hist(r, density=True, bins='auto', histtype='stepfilled', alpha=0.2)\n",
      ">>> ax.set_xlim([x[0], x[-1]])\n",
      ">>> ax.legend(loc='best', frameon=False)\n",
      ">>> plt.show()\n",
      "\n",
      "\n",
      "References\n",
      "----------\n",
      ".. [1] A. Azzalini and A. Capitanio (1999). Statistical applications of\n",
      "    the multivariate skew-normal distribution. J. Roy. Statist. Soc.,\n",
      "    B 61, 579-602. :arxiv:`0911.2093`\n",
      "\u001b[1;31mClass docstring:\u001b[0m\n",
      "A skew-normal random variable.\n",
      "\n",
      "%(before_notes)s\n",
      "\n",
      "Notes\n",
      "-----\n",
      "The pdf is::\n",
      "\n",
      "    skewnorm.pdf(x, a) = 2 * norm.pdf(x) * norm.cdf(a*x)\n",
      "\n",
      "`skewnorm` takes a real number :math:`a` as a skewness parameter\n",
      "When ``a = 0`` the distribution is identical to a normal distribution\n",
      "(`norm`). `rvs` implements the method of [1]_.\n",
      "\n",
      "%(after_notes)s\n",
      "\n",
      "%(example)s\n",
      "\n",
      "References\n",
      "----------\n",
      ".. [1] A. Azzalini and A. Capitanio (1999). Statistical applications of\n",
      "    the multivariate skew-normal distribution. J. Roy. Statist. Soc.,\n",
      "    B 61, 579-602. :arxiv:`0911.2093`\n",
      "\u001b[1;31mCall docstring:\u001b[0m \n",
      "Freeze the distribution for the given arguments.\n",
      "\n",
      "Parameters\n",
      "----------\n",
      "arg1, arg2, arg3,... : array_like\n",
      "    The shape parameter(s) for the distribution.  Should include all\n",
      "    the non-optional arguments, may include ``loc`` and ``scale``.\n",
      "\n",
      "Returns\n",
      "-------\n",
      "rv_frozen : rv_frozen instance\n",
      "    The frozen distribution."
     ]
    }
   ],
   "source": [
    "stats.skewnorm?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_histogram_normal_skewed(mean, standard_deviation, skew, color):\n",
    "    distribution = stats.skewnorm(skew, loc=mean, scale=standard_deviation)\n",
    "    normal_values = pd.DataFrame({\"value\": distribution.rvs(10000)})\n",
    "\n",
    "    chart = alt.Chart(normal_values).mark_bar().encode(\n",
    "        alt.X(\"value\", bin=alt.Bin(maxbins=100)),\n",
    "        y='count()',\n",
    "        color=alt.value(color)\n",
    "    )\n",
    "    return chart"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "  #altair-viz-7a12d584c2224fe9a26faef359526e9c.vega-embed {\n",
       "    width: 100%;\n",
       "    display: flex;\n",
       "  }\n",
       "\n",
       "  #altair-viz-7a12d584c2224fe9a26faef359526e9c.vega-embed details,\n",
       "  #altair-viz-7a12d584c2224fe9a26faef359526e9c.vega-embed details summary {\n",
       "    position: relative;\n",
       "  }\n",
       "</style>\n",
       "<div id=\"altair-viz-7a12d584c2224fe9a26faef359526e9c\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-7a12d584c2224fe9a26faef359526e9c\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-7a12d584c2224fe9a26faef359526e9c\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm/vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm/vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm/vega-lite@5.16.3?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm/vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function maybeLoadScript(lib, version) {\n",
       "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
       "      return (VEGA_DEBUG[key] == version) ?\n",
       "        Promise.resolve(paths[lib]) :\n",
       "        new Promise(function(resolve, reject) {\n",
       "          var s = document.createElement('script');\n",
       "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "          s.async = true;\n",
       "          s.onload = () => {\n",
       "            VEGA_DEBUG[key] = version;\n",
       "            return resolve(paths[lib]);\n",
       "          };\n",
       "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "          s.src = paths[lib];\n",
       "        });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else {\n",
       "      maybeLoadScript(\"vega\", \"5\")\n",
       "        .then(() => maybeLoadScript(\"vega-lite\", \"5.16.3\"))\n",
       "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 300, \"continuousHeight\": 300}}, \"data\": {\"url\": \"altair-data-23838dfebf665f412a4579e1afce3290.json\", \"format\": {\"type\": \"json\"}}, \"mark\": {\"type\": \"bar\"}, \"encoding\": {\"color\": {\"value\": \"blue\"}, \"x\": {\"bin\": {\"maxbins\": 100}, \"field\": \"value\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"type\": \"quantitative\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.16.3.json\"}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "plot_histogram_normal_skewed(0, 1, 2, \"blue\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For seeing the kurtosis in action, let's look at some data. We will load in the AAPL stock price from a h5 file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "Could not convert 'Open' to NumPy timedelta",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\_libs\\tslibs\\timedeltas.pyx:433\u001b[0m, in \u001b[0;36mpandas._libs.tslibs.timedeltas.array_to_timedelta64\u001b[1;34m()\u001b[0m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\_libs\\tslibs\\timedeltas.pyx:465\u001b[0m, in \u001b[0;36mpandas._libs.tslibs.timedeltas._item_to_timedelta64_fastpath\u001b[1;34m()\u001b[0m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\_libs\\tslibs\\timedeltas.pyx:637\u001b[0m, in \u001b[0;36mpandas._libs.tslibs.timedeltas.parse_timedelta_string\u001b[1;34m()\u001b[0m\n",
      "\u001b[1;31mValueError\u001b[0m: unit abbreviation w/o a number",
      "\nDuring handling of the above exception, another exception occurred:\n",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\_libs\\tslibs\\timedeltas.pyx:473\u001b[0m, in \u001b[0;36mpandas._libs.tslibs.timedeltas._item_to_timedelta64\u001b[1;34m()\u001b[0m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\_libs\\tslibs\\timedeltas.pyx:356\u001b[0m, in \u001b[0;36mpandas._libs.tslibs.timedeltas.convert_to_timedelta64\u001b[1;34m()\u001b[0m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\_libs\\tslibs\\timedeltas.pyx:637\u001b[0m, in \u001b[0;36mpandas._libs.tslibs.timedeltas.parse_timedelta_string\u001b[1;34m()\u001b[0m\n",
      "\u001b[1;31mValueError\u001b[0m: unit abbreviation w/o a number",
      "\nThe above exception was the direct cause of the following exception:\n",
      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[50], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m aapl \u001b[38;5;241m=\u001b[39m \u001b[43mpd\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread_hdf\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mdata/AAPL.h5\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\io\\pytables.py:442\u001b[0m, in \u001b[0;36mread_hdf\u001b[1;34m(path_or_buf, key, mode, errors, where, start, stop, columns, iterator, chunksize, **kwargs)\u001b[0m\n\u001b[0;32m    437\u001b[0m                 \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[0;32m    438\u001b[0m                     \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mkey must be provided when HDF5 \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    439\u001b[0m                     \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mfile contains multiple datasets.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m    440\u001b[0m                 )\n\u001b[0;32m    441\u001b[0m         key \u001b[38;5;241m=\u001b[39m candidate_only_group\u001b[38;5;241m.\u001b[39m_v_pathname\n\u001b[1;32m--> 442\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mstore\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mselect\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    443\u001b[0m \u001b[43m        \u001b[49m\u001b[43mkey\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    444\u001b[0m \u001b[43m        \u001b[49m\u001b[43mwhere\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mwhere\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    445\u001b[0m \u001b[43m        \u001b[49m\u001b[43mstart\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstart\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    446\u001b[0m \u001b[43m        \u001b[49m\u001b[43mstop\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstop\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    447\u001b[0m \u001b[43m        \u001b[49m\u001b[43mcolumns\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcolumns\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    448\u001b[0m \u001b[43m        \u001b[49m\u001b[43miterator\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43miterator\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    449\u001b[0m \u001b[43m        \u001b[49m\u001b[43mchunksize\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mchunksize\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    450\u001b[0m \u001b[43m        \u001b[49m\u001b[43mauto_close\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mauto_close\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m    451\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    452\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m (\u001b[38;5;167;01mValueError\u001b[39;00m, \u001b[38;5;167;01mTypeError\u001b[39;00m, \u001b[38;5;167;01mKeyError\u001b[39;00m):\n\u001b[0;32m    453\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(path_or_buf, HDFStore):\n\u001b[0;32m    454\u001b[0m         \u001b[38;5;66;03m# if there is an error, close the store if we opened it.\u001b[39;00m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\io\\pytables.py:872\u001b[0m, in \u001b[0;36mHDFStore.select\u001b[1;34m(self, key, where, start, stop, columns, iterator, chunksize, auto_close)\u001b[0m\n\u001b[0;32m    858\u001b[0m \u001b[38;5;66;03m# create the iterator\u001b[39;00m\n\u001b[0;32m    859\u001b[0m it \u001b[38;5;241m=\u001b[39m TableIterator(\n\u001b[0;32m    860\u001b[0m     \u001b[38;5;28mself\u001b[39m,\n\u001b[0;32m    861\u001b[0m     s,\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m    869\u001b[0m     auto_close\u001b[38;5;241m=\u001b[39mauto_close,\n\u001b[0;32m    870\u001b[0m )\n\u001b[1;32m--> 872\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mit\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_result\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\io\\pytables.py:1947\u001b[0m, in \u001b[0;36mTableIterator.get_result\u001b[1;34m(self, coordinates)\u001b[0m\n\u001b[0;32m   1944\u001b[0m     where \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mwhere\n\u001b[0;32m   1946\u001b[0m \u001b[38;5;66;03m# directly return the result\u001b[39;00m\n\u001b[1;32m-> 1947\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstart\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstop\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mwhere\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1948\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mclose()\n\u001b[0;32m   1949\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m results\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\io\\pytables.py:856\u001b[0m, in \u001b[0;36mHDFStore.select.<locals>.func\u001b[1;34m(_start, _stop, _where)\u001b[0m\n\u001b[0;32m    855\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mfunc\u001b[39m(_start, _stop, _where):\n\u001b[1;32m--> 856\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43ms\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mstart\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m_start\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstop\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m_stop\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mwhere\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m_where\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcolumns\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcolumns\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\io\\pytables.py:3219\u001b[0m, in \u001b[0;36mBlockManagerFixed.read\u001b[1;34m(self, where, columns, start, stop)\u001b[0m\n\u001b[0;32m   3215\u001b[0m dfs \u001b[38;5;241m=\u001b[39m []\n\u001b[0;32m   3217\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m i \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mnblocks):\n\u001b[1;32m-> 3219\u001b[0m     blk_items \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread_index\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mblock\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mi\u001b[49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m_items\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[0;32m   3220\u001b[0m     values \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mread_array(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mblock\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mi\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m_values\u001b[39m\u001b[38;5;124m\"\u001b[39m, start\u001b[38;5;241m=\u001b[39m_start, stop\u001b[38;5;241m=\u001b[39m_stop)\n\u001b[0;32m   3222\u001b[0m     columns \u001b[38;5;241m=\u001b[39m items[items\u001b[38;5;241m.\u001b[39mget_indexer(blk_items)]\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\io\\pytables.py:2917\u001b[0m, in \u001b[0;36mGenericFixed.read_index\u001b[1;34m(self, key, start, stop)\u001b[0m\n\u001b[0;32m   2915\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m variety \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mregular\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[0;32m   2916\u001b[0m     node \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mgroup, key)\n\u001b[1;32m-> 2917\u001b[0m     index \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mread_index_node\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnode\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstart\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstart\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mstop\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mstop\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   2918\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m index\n\u001b[0;32m   2919\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:  \u001b[38;5;66;03m# pragma: no cover\u001b[39;00m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\io\\pytables.py:3020\u001b[0m, in \u001b[0;36mGenericFixed.read_index_node\u001b[1;34m(self, node, start, stop)\u001b[0m\n\u001b[0;32m   3012\u001b[0m     index \u001b[38;5;241m=\u001b[39m factory(\n\u001b[0;32m   3013\u001b[0m         _unconvert_index(\n\u001b[0;32m   3014\u001b[0m             data, kind, encoding\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mencoding, errors\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39merrors\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m   3017\u001b[0m         \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs,\n\u001b[0;32m   3018\u001b[0m     )\n\u001b[0;32m   3019\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m-> 3020\u001b[0m     index \u001b[38;5;241m=\u001b[39m \u001b[43mfactory\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   3021\u001b[0m \u001b[43m        \u001b[49m\u001b[43m_unconvert_index\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   3022\u001b[0m \u001b[43m            \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkind\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencoding\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mencoding\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43merrors\u001b[49m\n\u001b[0;32m   3023\u001b[0m \u001b[43m        \u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3024\u001b[0m \u001b[43m        \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3025\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   3027\u001b[0m index\u001b[38;5;241m.\u001b[39mname \u001b[38;5;241m=\u001b[39m name\n\u001b[0;32m   3029\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m index\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\core\\indexes\\timedeltas.py:153\u001b[0m, in \u001b[0;36mTimedeltaIndex.__new__\u001b[1;34m(cls, data, unit, freq, closed, dtype, copy, name)\u001b[0m\n\u001b[0;32m    149\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m data\u001b[38;5;241m.\u001b[39m_view()\n\u001b[0;32m    151\u001b[0m \u001b[38;5;66;03m# - Cases checked above all return/raise before reaching here - #\u001b[39;00m\n\u001b[1;32m--> 153\u001b[0m tdarr \u001b[38;5;241m=\u001b[39m \u001b[43mTimedeltaArray\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_from_sequence_not_strict\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m    154\u001b[0m \u001b[43m    \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfreq\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mfreq\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43munit\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43munit\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdtype\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdtype\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcopy\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcopy\u001b[49m\n\u001b[0;32m    155\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    156\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mcls\u001b[39m\u001b[38;5;241m.\u001b[39m_simple_new(tdarr, name\u001b[38;5;241m=\u001b[39mname)\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\core\\arrays\\timedeltas.py:224\u001b[0m, in \u001b[0;36mTimedeltaArray._from_sequence_not_strict\u001b[1;34m(cls, data, dtype, copy, freq, unit)\u001b[0m\n\u001b[0;32m    220\u001b[0m freq \u001b[38;5;241m=\u001b[39m freq \u001b[38;5;28;01mif\u001b[39;00m freq \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m lib\u001b[38;5;241m.\u001b[39mno_default \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m    222\u001b[0m freq, freq_infer \u001b[38;5;241m=\u001b[39m dtl\u001b[38;5;241m.\u001b[39mmaybe_infer_freq(freq)\n\u001b[1;32m--> 224\u001b[0m data, inferred_freq \u001b[38;5;241m=\u001b[39m \u001b[43msequence_to_td64ns\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcopy\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mcopy\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43munit\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43munit\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    225\u001b[0m freq, freq_infer \u001b[38;5;241m=\u001b[39m dtl\u001b[38;5;241m.\u001b[39mvalidate_inferred_freq(freq, inferred_freq, freq_infer)\n\u001b[0;32m    226\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m explicit_none:\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\core\\arrays\\timedeltas.py:894\u001b[0m, in \u001b[0;36msequence_to_td64ns\u001b[1;34m(data, copy, unit, errors)\u001b[0m\n\u001b[0;32m    891\u001b[0m \u001b[38;5;66;03m# Convert whatever we have into timedelta64[ns] dtype\u001b[39;00m\n\u001b[0;32m    892\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m is_object_dtype(data\u001b[38;5;241m.\u001b[39mdtype) \u001b[38;5;129;01mor\u001b[39;00m is_string_dtype(data\u001b[38;5;241m.\u001b[39mdtype):\n\u001b[0;32m    893\u001b[0m     \u001b[38;5;66;03m# no need to make a copy, need to convert if string-dtyped\u001b[39;00m\n\u001b[1;32m--> 894\u001b[0m     data \u001b[38;5;241m=\u001b[39m \u001b[43m_objects_to_td64ns\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43munit\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43munit\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43merrors\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    895\u001b[0m     copy \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[0;32m    897\u001b[0m \u001b[38;5;28;01melif\u001b[39;00m is_integer_dtype(data\u001b[38;5;241m.\u001b[39mdtype):\n\u001b[0;32m    898\u001b[0m     \u001b[38;5;66;03m# treat as multiples of the given unit\u001b[39;00m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\core\\arrays\\timedeltas.py:1004\u001b[0m, in \u001b[0;36m_objects_to_td64ns\u001b[1;34m(data, unit, errors)\u001b[0m\n\u001b[0;32m   1001\u001b[0m \u001b[38;5;66;03m# coerce Index to np.ndarray, converting string-dtype if necessary\u001b[39;00m\n\u001b[0;32m   1002\u001b[0m values \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marray(data, dtype\u001b[38;5;241m=\u001b[39mnp\u001b[38;5;241m.\u001b[39mobject_, copy\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[1;32m-> 1004\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[43marray_to_timedelta64\u001b[49m\u001b[43m(\u001b[49m\u001b[43mvalues\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43munit\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43munit\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merrors\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43merrors\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1005\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m result\u001b[38;5;241m.\u001b[39mview(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtimedelta64[ns]\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\_libs\\tslibs\\timedeltas.pyx:447\u001b[0m, in \u001b[0;36mpandas._libs.tslibs.timedeltas.array_to_timedelta64\u001b[1;34m()\u001b[0m\n",
      "File \u001b[1;32mc:\\Python311\\Lib\\site-packages\\pandas\\_libs\\tslibs\\timedeltas.pyx:480\u001b[0m, in \u001b[0;36mpandas._libs.tslibs.timedeltas._item_to_timedelta64\u001b[1;34m()\u001b[0m\n",
      "\u001b[1;31mValueError\u001b[0m: Could not convert 'Open' to NumPy timedelta"
     ]
    }
   ],
   "source": [
    "aapl = pd.read_hdf(\"data/AAPL.h5\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'aapl' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[26], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43maapl\u001b[49m\u001b[38;5;241m.\u001b[39mhead()\n",
      "\u001b[1;31mNameError\u001b[0m: name 'aapl' is not defined"
     ]
    }
   ],
   "source": [
    "aapl.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercises\n",
    "\n",
    "1. Compute the increase in price for each day (Close - Open)\n",
    "2. Plot a histogram of these increases\n",
    "3. Investigate the `stats.skew` and `stats.kurtosis` functions to compute the third and fourth moment of the dataset.\n",
    "\n",
    "*For solutions, see `solutions/moments.py`*\n",
    "\n",
    "#### Extended exercise\n",
    "\n",
    "Quandl has a python module for extracting datasets. The documentation is available at https://www.quandl.com/tools/python\n",
    "\n",
    "Install this module, and review the documentation to obtain stock prices for the following four tech giants:\n",
    "* IBM\n",
    "* Google\n",
    "* Apple (more up-to-date than our dataset)\n",
    "* Amazon\n",
    "\n",
    "Compute the skew and kurtosis of each stock, and compare the results. Looking at the histograms of the stock prices, the skew and the kurtosis, what does this tell you about the usefulness of these moments?\n",
    "\n",
    "Note: Extended exercises are more open-ended than normal exercises, and may take significantly longer to complete. They also tend to be harder than other exercises. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-fdbd9f21dbdc4c249bb72c528a4d2c71\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-fdbd9f21dbdc4c249bb72c528a4d2c71\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-fdbd9f21dbdc4c249bb72c528a4d2c71\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.17.0?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function maybeLoadScript(lib, version) {\n",
       "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
       "      return (VEGA_DEBUG[key] == version) ?\n",
       "        Promise.resolve(paths[lib]) :\n",
       "        new Promise(function(resolve, reject) {\n",
       "          var s = document.createElement('script');\n",
       "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "          s.async = true;\n",
       "          s.onload = () => {\n",
       "            VEGA_DEBUG[key] = version;\n",
       "            return resolve(paths[lib]);\n",
       "          };\n",
       "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "          s.src = paths[lib];\n",
       "        });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else {\n",
       "      maybeLoadScript(\"vega\", \"5\")\n",
       "        .then(() => maybeLoadScript(\"vega-lite\", \"4.17.0\"))\n",
       "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"url\": \"altair-data-ffc129b5e5987ef92a23c6ab888126df.json\", \"format\": {\"type\": \"json\"}}, \"mark\": \"bar\", \"encoding\": {\"color\": {\"value\": \"red\"}, \"x\": {\"bin\": {\"maxbins\": 100}, \"field\": \"Price_Change\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"type\": \"quantitative\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.17.0.json\"}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Skew: -0.3589561655618472\n",
      "Kurtosis: 12.862787458978701\n"
     ]
    }
   ],
   "source": [
    "aapl['Price_Change'] = aapl['Close'] - aapl['Open']\n",
    "aapl.head()\n",
    "\n",
    "\n",
    "chart = alt.Chart(aapl).mark_bar().encode(\n",
    "        alt.X(\"Price_Change\", bin=alt.Bin(maxbins=100)),\n",
    "        y='count()',\n",
    "        color=alt.value('red'))\n",
    "chart.display()\n",
    "\n",
    "print(\"Skew: \" + str(stats.skew(aapl['Price_Change'])))\n",
    "print(\"Kurtosis: \" + str(stats.kurtosis(aapl['Price_Change'])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*For solutions, see `solutions/moments.py`*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Z-scores\n",
    "\n",
    "A \"z-score\" is a common normalisation method used for data. It removes the scale of the data, and instead considers the size of the data in terms of the standard deviation. It is a transformation of the data from one scale to another, using the mean and standard deviation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "original_data = np.array([10, 20, 5, 105, 30, 17, 19], dtype=np.float32)\n",
    "m = np.mean(original_data)\n",
    "s = np.std(original_data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The transformation is to subtract the mean, and divide by the standard deviation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "zscores = (original_data - m) / s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-0.612737  , -0.29735765, -0.77042663,  2.3833666 ,  0.01802167,\n",
       "       -0.39197144, -0.3288956 ], dtype=float32)"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "zscores"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The values of the z-scores are normalised, allowing us to compare data from different scales - for instance, comparing the stock prices between AAPL and MSFT for a period of one month, where direct comparisons are initially hard. \n",
    "\n",
    "Let's load some data from Quandl. To do that, create a file called `my_secrets.py` and create a value called `QUANDL_API_KEY` and set that equal to your API key from Quandl. You can obtain one by signing up at https://www.quandl.com/tools/api and then viewing your profile page at https://www.quandl.com/account/profile\n",
    "\n",
    "You can copy the file `my_secrets_template.py` to create this file for you. Just copy the file and fill out the data. Ensure this file is in the same directory as your notebooks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing my_secrets.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile my_secrets.py\n",
    "\n",
    "QUANDL_API_KEY = \"ue4SAPctpsjD3UJYZ2o1\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [],
   "source": [
    "import quandl\n",
    "import my_secrets\n",
    "quandl.ApiConfig.api_key = my_secrets.QUANDL_API_KEY"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = quandl.get_table('WIKI/PRICES', ticker = ['MSFT', 'AAPL'], \n",
    "                        qopts = { 'columns': ['ticker', 'date', 'adj_close'] }, \n",
    "                        date = { 'gte': '2017-01-01', 'lte': '2019-01-01' }, \n",
    "                        paginate=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "pandas.core.frame.DataFrame"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.sample(5)\n",
    "type(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we compare the means, we see that AAPL has a higher adjusted close value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ticker\n",
       "AAPL    154.137248\n",
       "MSFT     75.098922\n",
       "Name: adj_close, dtype: float64"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.groupby(\"ticker\")['adj_close'].mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, we might be more interested to see whether movements swing wildly, or are stable with regard to the current price."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<style>\n",
       "  #altair-viz-b43b9cb3bcbd4228b8efb60e2cf1dfcd.vega-embed {\n",
       "    width: 100%;\n",
       "    display: flex;\n",
       "  }\n",
       "\n",
       "  #altair-viz-b43b9cb3bcbd4228b8efb60e2cf1dfcd.vega-embed details,\n",
       "  #altair-viz-b43b9cb3bcbd4228b8efb60e2cf1dfcd.vega-embed details summary {\n",
       "    position: relative;\n",
       "  }\n",
       "</style>\n",
       "<div id=\"altair-viz-b43b9cb3bcbd4228b8efb60e2cf1dfcd\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-b43b9cb3bcbd4228b8efb60e2cf1dfcd\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-b43b9cb3bcbd4228b8efb60e2cf1dfcd\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm/vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm/vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm/vega-lite@5.16.3?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm/vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function maybeLoadScript(lib, version) {\n",
       "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
       "      return (VEGA_DEBUG[key] == version) ?\n",
       "        Promise.resolve(paths[lib]) :\n",
       "        new Promise(function(resolve, reject) {\n",
       "          var s = document.createElement('script');\n",
       "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "          s.async = true;\n",
       "          s.onload = () => {\n",
       "            VEGA_DEBUG[key] = version;\n",
       "            return resolve(paths[lib]);\n",
       "          };\n",
       "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "          s.src = paths[lib];\n",
       "        });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else {\n",
       "      maybeLoadScript(\"vega\", \"5\")\n",
       "        .then(() => maybeLoadScript(\"vega-lite\", \"5.16.3\"))\n",
       "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 300, \"continuousHeight\": 300}}, \"data\": {\"url\": \"altair-data-dcff4fcf6cf0d7b6d2eaf54edc56ae09.json\", \"format\": {\"type\": \"json\"}}, \"mark\": {\"type\": \"bar\", \"opacity\": 0.4}, \"encoding\": {\"color\": {\"field\": \"ticker\", \"type\": \"nominal\"}, \"x\": {\"bin\": {\"maxbins\": 30}, \"field\": \"adj_close\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"stack\": null, \"type\": \"quantitative\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v5.16.3.json\"}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "alt.Chart(data).mark_bar(opacity=0.4).encode(\n",
    "    x=alt.X(\"adj_close\", bin=alt.Bin(maxbins=30)),\n",
    "    y=alt.Y('count()', stack=None),\n",
    "    # column='ticker',\n",
    "    color='ticker',\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To truly compare these distributions, we need to convert them to z-scores first, which gives us more information about the relative stock price movements:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Index(['ticker', 'date', 'adj_close'], dtype='object')"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th>ticker</th>\n",
       "      <th>AAPL</th>\n",
       "      <th>MSFT</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>date</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>2017-01-03</td>\n",
       "      <td>-2.399152</td>\n",
       "      <td>-1.330589</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2017-01-04</td>\n",
       "      <td>-2.406966</td>\n",
       "      <td>-1.356847</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2017-01-05</td>\n",
       "      <td>-2.371503</td>\n",
       "      <td>-1.356847</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2017-01-06</td>\n",
       "      <td>-2.293364</td>\n",
       "      <td>-1.306206</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2017-01-09</td>\n",
       "      <td>-2.228449</td>\n",
       "      <td>-1.324962</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "ticker          AAPL      MSFT\n",
       "date                          \n",
       "2017-01-03 -2.399152 -1.330589\n",
       "2017-01-04 -2.406966 -1.356847\n",
       "2017-01-05 -2.371503 -1.356847\n",
       "2017-01-06 -2.293364 -1.306206\n",
       "2017-01-09 -2.228449 -1.324962"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prices = data.pivot(columns=\"ticker\", index=\"date\", values='adj_close')\n",
    "z_scores = (prices - prices.mean())/prices.std()\n",
    "z_scores.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-62e043d3cd544dec8ed6c25194f9558c\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-62e043d3cd544dec8ed6c25194f9558c\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-62e043d3cd544dec8ed6c25194f9558c\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.17.0?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function maybeLoadScript(lib, version) {\n",
       "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
       "      return (VEGA_DEBUG[key] == version) ?\n",
       "        Promise.resolve(paths[lib]) :\n",
       "        new Promise(function(resolve, reject) {\n",
       "          var s = document.createElement('script');\n",
       "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "          s.async = true;\n",
       "          s.onload = () => {\n",
       "            VEGA_DEBUG[key] = version;\n",
       "            return resolve(paths[lib]);\n",
       "          };\n",
       "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "          s.src = paths[lib];\n",
       "        });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else {\n",
       "      maybeLoadScript(\"vega\", \"5\")\n",
       "        .then(() => maybeLoadScript(\"vega-lite\", \"4.17.0\"))\n",
       "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"url\": \"altair-data-c30b316f8117097027d792220517ecd7.json\", \"format\": {\"type\": \"json\"}}, \"mark\": {\"type\": \"bar\", \"opacity\": 0.4}, \"encoding\": {\"color\": {\"field\": \"ticker\", \"type\": \"nominal\"}, \"x\": {\"bin\": {\"maxbins\": 30}, \"field\": \"z_score_adj_close\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"stack\": null, \"type\": \"quantitative\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.17.0.json\"}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "alt.Chart(z_scores.melt(value_name=\"z_score_adj_close\")).mark_bar(opacity=0.4).encode(\n",
    "    x=alt.X(\"z_score_adj_close\", bin=alt.Bin(maxbins=30)),\n",
    "    y=alt.Y('count()', stack=None),\n",
    "    # column='ticker',\n",
    "    color='ticker',\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now compare the distributions, visually and directly against each other. This specific analysis doesn't tell us much, but we can use z-scores to compare distributions of data from different scales, as we saw above."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Exercise\n",
    "\n",
    "Perform the same analysis, but using the increase in adjusted closing price in a given day, rather than the absolute value."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<div id=\"altair-viz-f062721bcfdc4d82b04e82a4f197ea66\"></div>\n",
       "<script type=\"text/javascript\">\n",
       "  var VEGA_DEBUG = (typeof VEGA_DEBUG == \"undefined\") ? {} : VEGA_DEBUG;\n",
       "  (function(spec, embedOpt){\n",
       "    let outputDiv = document.currentScript.previousElementSibling;\n",
       "    if (outputDiv.id !== \"altair-viz-f062721bcfdc4d82b04e82a4f197ea66\") {\n",
       "      outputDiv = document.getElementById(\"altair-viz-f062721bcfdc4d82b04e82a4f197ea66\");\n",
       "    }\n",
       "    const paths = {\n",
       "      \"vega\": \"https://cdn.jsdelivr.net/npm//vega@5?noext\",\n",
       "      \"vega-lib\": \"https://cdn.jsdelivr.net/npm//vega-lib?noext\",\n",
       "      \"vega-lite\": \"https://cdn.jsdelivr.net/npm//vega-lite@4.17.0?noext\",\n",
       "      \"vega-embed\": \"https://cdn.jsdelivr.net/npm//vega-embed@6?noext\",\n",
       "    };\n",
       "\n",
       "    function maybeLoadScript(lib, version) {\n",
       "      var key = `${lib.replace(\"-\", \"\")}_version`;\n",
       "      return (VEGA_DEBUG[key] == version) ?\n",
       "        Promise.resolve(paths[lib]) :\n",
       "        new Promise(function(resolve, reject) {\n",
       "          var s = document.createElement('script');\n",
       "          document.getElementsByTagName(\"head\")[0].appendChild(s);\n",
       "          s.async = true;\n",
       "          s.onload = () => {\n",
       "            VEGA_DEBUG[key] = version;\n",
       "            return resolve(paths[lib]);\n",
       "          };\n",
       "          s.onerror = () => reject(`Error loading script: ${paths[lib]}`);\n",
       "          s.src = paths[lib];\n",
       "        });\n",
       "    }\n",
       "\n",
       "    function showError(err) {\n",
       "      outputDiv.innerHTML = `<div class=\"error\" style=\"color:red;\">${err}</div>`;\n",
       "      throw err;\n",
       "    }\n",
       "\n",
       "    function displayChart(vegaEmbed) {\n",
       "      vegaEmbed(outputDiv, spec, embedOpt)\n",
       "        .catch(err => showError(`Javascript Error: ${err.message}<br>This usually means there's a typo in your chart specification. See the javascript console for the full traceback.`));\n",
       "    }\n",
       "\n",
       "    if(typeof define === \"function\" && define.amd) {\n",
       "      requirejs.config({paths});\n",
       "      require([\"vega-embed\"], displayChart, err => showError(`Error loading script: ${err.message}`));\n",
       "    } else {\n",
       "      maybeLoadScript(\"vega\", \"5\")\n",
       "        .then(() => maybeLoadScript(\"vega-lite\", \"4.17.0\"))\n",
       "        .then(() => maybeLoadScript(\"vega-embed\", \"6\"))\n",
       "        .catch(showError)\n",
       "        .then(() => displayChart(vegaEmbed));\n",
       "    }\n",
       "  })({\"config\": {\"view\": {\"continuousWidth\": 400, \"continuousHeight\": 300}}, \"data\": {\"url\": \"altair-data-be5783c31777849ca5f78ac125b3bfc1.json\", \"format\": {\"type\": \"json\"}}, \"mark\": {\"type\": \"bar\", \"opacity\": 0.4}, \"encoding\": {\"color\": {\"field\": \"ticker\", \"type\": \"nominal\"}, \"x\": {\"bin\": {\"maxbins\": 30}, \"field\": \"z_score_returns\", \"type\": \"quantitative\"}, \"y\": {\"aggregate\": \"count\", \"stack\": null, \"type\": \"quantitative\"}}, \"$schema\": \"https://vega.github.io/schema/vega-lite/v4.17.0.json\"}, {\"mode\": \"vega-lite\"});\n",
       "</script>"
      ],
      "text/plain": [
       "alt.Chart(...)"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "returns = prices.pct_change().iloc[1:,:]\n",
    "z_rets = (returns - returns.mean())/returns.std()\n",
    "\n",
    "alt.Chart(z_rets.melt(value_name=\"z_score_returns\")).mark_bar(opacity=0.4).encode(\n",
    "    x=alt.X(\"z_score_returns\", bin=alt.Bin(maxbins=30)),\n",
    "    y=alt.Y('count()', stack=None),\n",
    "    # column='ticker',\n",
    "    color='ticker',\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*For solutions, see `solutions/adjusted_increases.py`*"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
